package com.linln.admin.app.service.task;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.linln.admin.app.constants.AppConstants;
import com.linln.admin.app.constants.TaskTypeEnum;
import com.linln.admin.app.domain.AppInfo;
import com.linln.admin.app.domain.AppVersion;
import com.linln.admin.app.domain.RepoAccount;
import com.linln.admin.app.domain.VersionApp;
import com.linln.admin.app.service.branch.BranchHelper;
import com.linln.admin.system.domain.TaskQueue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.shared.invoker.*;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 将项目进行deploy
 */
@Service
@Slf4j
public class MvnDeployTask extends AbstractTaskExecutor {
    @Value("${mvn.home}")
    private String mvnHome;

    /**
     * 是否可以处理这个任务
     *
     * @param t
     * @return
     */
    @Override
    public boolean support(TaskQueue t) {
        return t != null && TaskTypeEnum.MVN_DEPLOY.getType().equals(t.getType());
    }

    /**
     * 处理任务，并返回处理结果
     *
     * @param t
     * @return true-成功; 其它-false
     */
    @Override
    public boolean exec(TaskQueue t) {
        String appIdJson = t.getData();
        if (StringUtils.isBlank(appIdJson)) {
            throw new IllegalArgumentException("请指定要deploy打包的应用！");
        }
        List<String> recIds = null;
        try {
            recIds = new ObjectMapper().readValue(appIdJson, new TypeReference<List<String>>() {
            });
        } catch (Exception e) {
        }
        if (recIds == null || recIds.isEmpty()) {
            return true;
        }

        Long vid = t.getRelate_id();
        Optional<AppVersion> ver = versionRepository.findById(vid);
        if (!ver.isPresent()) {
            throw new IllegalArgumentException("对应的版本信息不存在！");
        }
        AppVersion version = ver.get();

        List<Long> selectedRecIds = new ArrayList<>();
        for (String recId : recIds) {
            selectedRecIds.add(Long.parseLong(recId));
        }
        VersionApp va = new VersionApp();
        va.setVersion_id(vid);
        List<VersionApp> vas = versionAppRepository.findAll(Example.of(va));
        if (vas == null || vas.isEmpty()) {
            throw new IllegalArgumentException("没有关联的项目信息");
        }
        if ("2".equals(version.getPublished())) {
            t.setMsg("当前版本已经废弃，不能打包，否则会容易影响正常项目的包环境！");
            return true;
        }

        String baseVer = version.getVersion().trim().replace(" ", "");
        if (baseVer.startsWith(AppConstants.BRANCH_DEV_PREFIX)) {
            baseVer = baseVer.substring(4);
        }
        String tarBranch = AppConstants.BRANCH_DEV_PREFIX + baseVer;
        if (AppConstants.YES.equals(version.getHotfix_flag())) {
            tarBranch = AppConstants.BRANCH_HOTFIX_PREFIX + baseVer;
        } else if (AppConstants.YES.equals(version.getCo_release_branch())) {
            tarBranch = AppConstants.BRANCH_RELEASE_PREFIX + baseVer;
        }


        for (VersionApp v : vas) {
            if (!selectedRecIds.contains(v.getId())) {
                continue;//如果不是选中的，就不需要进行操作。
            }
            Optional<AppInfo> app = infoRepository.findById(v.getApp_id());
            if (!app.isPresent()) {
                continue;
            }
            StringBuilder ret = new StringBuilder("[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "] ");
            try {
                if (AppConstants.VERSION_MERGE_STATUS_CONFLICT.equals(v.getMerge_from_status()) || AppConstants.VERSION_MERGE_STATUS_CONFLICT.equals(v.getMerge_to_status())) {
                    ret.append("存在合并冲突未解决，请先处理完冲突再试一下。");
                    continue;
                }
                //开始拉取项目代码，并扫描需要打包的项目
                File dir = null;
                try {//将release代码切出来
                    Optional<RepoAccount> ora = accountRepository.findById(app.get().getRepo_account_id());
                    RepoAccount account = ora.isPresent() ? ora.get() : null;
                    dir = BranchHelper.findHandler(app.get().getRepo_type()).checkoutBranch(baseVer, app.get().getRepo_url(), account.getAccount(), account.getPass(), tarBranch);
                    //判断有没有snapshot引用，有的话不能deploy
                    StringBuilder msg = new StringBuilder();
                    CheckPmdTask.doCheckSnapshot(dir, msg);
                    if (msg.length() > 0) {
                        ret.append("应用中有snapshot引用，取消打包操作<br>" + msg.toString().replace("\n", "<br>"));
                        continue;
                    }
                    doDeploy(dir, ret);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    ret.append("发生错误！" + e.getMessage());
                } finally {
                    try {
                        FileUtils.deleteDirectory(dir);
                    } catch (Exception e) {
                        log.warn(e.getMessage(), e);
                    }
                }
            } finally {
                v.setDeploy(ret.toString());
                versionAppRepository.save(v);
            }
        }
        return true;
    }

    /**
     * 将仓库中的所有pom项目进行deploy
     *
     * @param dir 项目仓库代码目录，里面有Src等目录
     * @param ret
     * @throws Exception
     */
    private void doDeploy(File dir, StringBuilder ret) throws Exception {
        //先查找pom文件
        Collection<File> poms = FileUtils.listFiles(dir, new NameFileFilter("pom.xml"), TrueFileFilter.TRUE);
        if (poms == null || poms.isEmpty()) {
            throw new IllegalArgumentException("项目中没有找到pom文件！");
        }

        List<String> dirNames = readGitkeep(dir);
        //只留下-api和-starter的，或者 .gitkeep里面定义的目录
        //过滤掉子项目的pom文件
        List<File> dirs = new ArrayList<>();
        for (File pom : poms) {
            File pomDir = pom.getParentFile();
            String dirName = pomDir.getName();
            if (dirNames != null && dirNames.size() > 0) {//如果gikkeep文件中指定了文件夹或项目名，则直接使用，不用管其它项目
                if (dirNames.contains(dirName)) {
                    dirs.add(pomDir);
                }
            } else {//没有指定的，一律扫描
                if (!isSkipDeploy(pom)) {
                    dirs.add(pomDir);
                }
            }
        }
        if (CollectionUtils.isEmpty(dirNames)) {//如果是自动扫描的，则根据pom中的module定义，将module去除，以免重复deploy
            for (File pom : poms) {
                List<String> modules = readModuleNames(pom);
                if (CollectionUtils.isEmpty(modules)) {
                    continue;
                }
                //指定了modules，说明是父结点的pom文件
                if (isSkipDeploy(pom)) {
                    //如果标记了不deploy，则所有的子模块也都要排除掉
                    dirs.removeAll(modules);//把module删除
                } else {
                    //否则排除自己，因为父类自己不需要deploy
                    dirs.remove(pom.getParentFile());
                }
            }
        }
        if (dirs.isEmpty()) {
            ret.append(" 没有可以打包的模块，请在.gitkeep中指定！");
        }
        //排序
        dirs = sortDirs(dirNames, dirs);
        //分别进行打包
        for (File prjDir : dirs) {
            String errMsg = mvnDeploy(prjDir);
            ret.append("· " + prjDir.getName());
            if (StringUtils.isBlank(errMsg)) {
                ret.append(" 打包成功！");
            } else {
                ret.append(" 失败，" + errMsg);
            }
            ret.append("<br>");
        }
    }

    /**
     * 读取pom文件中的下级的模块名称
     */
    private List<String> readModuleNames(File pom) {
        try {
            SAXReader reader = new SAXReader();
            // 读取 xml 文件并解析成 Document 对象
            Document document = reader.read(pom);
            // 获取根节点
            Element root = document.getRootElement();
            // 找到项目下的 modules 元素
            Element modules = root.element("modules");
            if (modules == null) {
                return null;
            }
            // 获取 modules 元素下的所有子元素
            List<Element> moduleElements = modules.elements();
            if (CollectionUtils.isEmpty(moduleElements)) {
                return null;
            }
            // 遍历所有子元素，并输出其名称
            List<String> ret = new ArrayList<>();
            for (Element module : moduleElements) {
                ret.add(module.getTextTrim());
            }
            return ret;
        } catch (Exception e) {
            log.warn("读取pom文件失败：" + e, e);
        }
        return null;
    }

    /**
     * 将pom中定义了不需要deploy的去除掉
     */
    private boolean isSkipDeploy(File pom) {
        try {
            String pomContent = FileUtils.readFileToString(pom, "UTF-8");
            return pomContent.contains("<skip_maven_deploy>true</skip_maven_deploy>") || pomContent.contains("<maven.deploy.skip>true</maven.deploy.skip>");
        } catch (IOException e) {
            return false;//出错了，就表示需要处理
        }
    }

    /**
     * 按照 .gitkeep 里面定义的顺序进行排序
     *
     * @param dirNames 指定的目录顺序
     * @param dirs     所有pom所在的目录
     * @return 返回排序好的目录列表
     */
    private List<File> sortDirs(List<String> dirNames, List<File> dirs) {
        Map<String, File> dirMap = dirs.stream().collect(Collectors.toMap(File::getName, (d) -> d));
        List<File> tmp = new ArrayList<>();
        for (String dirName : dirNames) {
            if (dirMap.containsKey(dirName)) {
                tmp.add(dirMap.get(dirName));
                dirMap.remove(dirName);
            }
        }
        if (dirMap.size() > 0) {
            tmp.addAll(dirMap.values());
        }
        return tmp;
    }

    /**
     * 读取.gitkeep中的有效内容
     *
     * @param dir
     * @return
     */
    private List<String> readGitkeep(File dir) {
        List<String> dirNames = new ArrayList<>();
        //读取.gitkeep中的内容
        File gitKeep = new File(dir, "Src/.gitkeep");
        if (gitKeep.isFile()) {
            try {
                List<String> tmp = FileUtils.readLines(gitKeep, "UTF-8");
                for (String dirname : tmp) {
                    dirname = dirname.trim();
                    if (dirname.isEmpty() || dirname.startsWith("#") || dirname.startsWith("*")) {
                        continue;
                    }
                    dirNames.add(dirname);
                }
            } catch (Exception e) {
            }
        }
        return dirNames;
    }

    /**
     * 运行deploy命令
     *
     * @param prjDir 父项目所在目录
     * @return 成功返回空；失败返回失败原因
     */
    private String mvnDeploy(File prjDir) {
        log.info("开始打包项目：{}", new File(prjDir, "pom.xml").getPath());
        InvocationRequest request = new DefaultInvocationRequest();
        request.setPomFile(new File(prjDir, "pom.xml"));
        request.setGoals(Lists.newArrayList("clean", "deploy", "-DskipTests"));
        MavenInvokerBean.EmptyInvocationOutputHandler handler = new MavenInvokerBean.EmptyInvocationOutputHandler();
        request.setOutputHandler(handler);

        Invoker invoker = new DefaultInvoker();
        invoker.setMavenHome(new File(mvnHome));
        invoker.setLogger(new Slf4jLogger());
        try {
            InvocationResult result = invoker.execute(request);
            return result == null ? "打包失败！" : result.getExitCode() == 0 ? "" : result.getExecutionException() == null ? ("打包失败：" + handler.getErrorMessage()) : result.getExecutionException().getMessage();
        } catch (Exception e) {
            log.error("mvn deploy失败:{}", e.getMessage(), e);
            return e.getMessage();
        }
    }

    public class Slf4jLogger implements InvokerLogger {
        @Override
        public void debug(String message) {
            log.debug(message);
        }

        @Override
        public void debug(String message, Throwable throwable) {
            log.debug(message, throwable);
        }

        @Override
        public boolean isDebugEnabled() {
            return log.isDebugEnabled();
        }

        @Override
        public void info(String message) {
            log.info(message);
        }

        @Override
        public void info(String message, Throwable throwable) {
            log.info(message, throwable);
        }

        @Override
        public boolean isInfoEnabled() {
            return log.isInfoEnabled();
        }

        @Override
        public void warn(String message) {
            log.warn(message);
        }

        @Override
        public void warn(String message, Throwable throwable) {
            log.warn(message, throwable);
        }

        @Override
        public boolean isWarnEnabled() {
            return log.isWarnEnabled();
        }

        @Override
        public void error(String message) {
            log.error(message);
        }

        @Override
        public void error(String message, Throwable throwable) {
            log.error(message, throwable);
        }

        @Override
        public boolean isErrorEnabled() {
            return log.isErrorEnabled();
        }

        @Override
        public void fatalError(String message) {
            log.error(message);
        }

        @Override
        public void fatalError(String message, Throwable throwable) {
            log.error(message, throwable);
        }

        @Override
        public boolean isFatalErrorEnabled() {
            return log.isErrorEnabled();
        }

        @Override
        public void setThreshold(int threshold) {

        }

        @Override
        public int getThreshold() {
            return 0;
        }
    }
}
